package main

import (
	"fmt"
	tp "gitee.com/swsk33/concurrent-task-pool/v2"
	"gitee.com/swsk33/gopher-fetch"
	"gitee.com/swsk33/sclog"
	"gitee.com/swsk33/sentinel-fetch/api"
	"gitee.com/swsk33/sentinel-fetch/global"
	"gitee.com/swsk33/sentinel-fetch/model"
	"gitee.com/swsk33/sentinel-fetch/util"
	"github.com/fatih/color"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	// 任务进度文件位置
	taskFilePath = "task.json"
	// 原始下载列表
	downloadTaskFolder = "fetch-list"
)

// 读取原始的下载任务列表
func readOriginList() []string {
	// 读取原始下载列表
	fileList := make([]string, 0)
	util.ListAllFile(downloadTaskFolder, &fileList)
	sclog.InfoLine("读取原始任务列表...")
	// 收集全部文件中的identifier的切片
	collectList := make([]string, 0)
	for _, file := range fileList {
		data, e := util.ReadDataFromFile(file)
		if e != nil {
			sclog.Error("读取文件出错！%s\n", e)
			continue
		}
		// 拆分为行
		dataString := string(data)
		lines := strings.Split(dataString, "\n")
		// 加入地址到结果列表
		collectList = append(collectList, lines...)
	}
	// 使用map去重
	identifierMap := make(map[string]any)
	// 处理identifier列表
	for _, item := range collectList {
		item := strings.TrimSpace(item)
		if item != "" {
			identifierMap[item] = nil
		}
	}
	// 加入到结果列表
	resultList := make([]string, 0, len(identifierMap))
	for item := range identifierMap {
		resultList = append(resultList, item)
	}
	sclog.Info("获取到%d个遥感影像！\n", len(resultList))
	return resultList
}

// 将原始的下载目录任务解析为具体下载文件任务
func getDownloadUrl() []*model.GranuleTask {
	// 获取原始链接列表
	originList := readOriginList()
	// 解析全部下载路径的具体文件地址
	sclog.InfoLine("正在获取下载地址...")
	// 全部结果
	totalTaskList := make([]*model.GranuleTask, 0)
	// 拆分列表为500个一组
	batchCount := 500
	splitList := util.SplitSlice(originList, batchCount)
	for index, list := range splitList {
		end := (index + 1) * batchCount
		if end > len(originList) {
			end = len(originList)
		}
		// 查询具体详细元数据
		sclog.Info("正在查询第%d - %d个影像下载地址...\n", index*batchCount, end)
		result, e := api.QueryByIdentifierList(list)
		if e != nil {
			sclog.ErrorLine(e.Error())
			sclog.WarnLine("忽略该批次...")
			continue
		}
		// 检查每个影像元数据的下载地址
		for _, item := range result.Value {
			// 判断影像能否下载
			if !item.Online || len(item.Locations) == 0 {
				sclog.Warn("影像%s不可用，不加入到下载列表！", item.Name)
				continue
			}
			// 获取下载地址
			url := ""
			for _, location := range item.Locations {
				if location.FormatType == "Extracted" {
					url = location.DownloadLink
					break
				}
			}
			// 获取MD5摘要
			md5 := ""
			for _, checksum := range item.Checksum {
				if strings.ToUpper(checksum.Algorithm) == "MD5" {
					md5 = checksum.Value
					break
				}
			}
			if url == "" {
				sclog.Warn("影像%s无下载列表，不加入到下载列表！", item.Name)
				continue
			}
			// 替换下载地址部分
			url = strings.Replace(url, "catalogue", "download", 1)
			// 创建任务
			totalTaskList = append(totalTaskList, model.NewGranuleTask(item.Name, url, md5))
		}
	}
	sclog.Info("已查询到%d个影像的下载地址！\n", len(totalTaskList))
	return totalTaskList
}

// 开始下载任务
//
// 若正常下载执行完成，返回true，否则被中断返回false
func fetchFiles(taskList []*model.GranuleTask) bool {
	// 先登录并获取Token，启用自动刷新
	e := util.EnableTokenRefresh()
	if e != nil {
		sclog.ErrorLine(e.Error())
		return false
	}
	// 用于下载时的日志输出器
	realTimeLogger := util.NewRealTimeStatusLogger(color.FgHiBlue)
	// 阶段状态日志打印器，共享锁
	logger := sclog.NewMutexLoggerShareLock(&realTimeLogger.Lock)
	// 准备创建下载任务
	fetchTaskPool := tp.NewTaskPool(global.TotalConfig.Download.ParallelCount, 0, 2*time.Second, taskList,
		func(task *model.GranuleTask, pool *tp.TaskPool[*model.GranuleTask]) {
			// 计算下载路径
			filePath := filepath.Join(global.TotalConfig.Download.Folder, fmt.Sprintf("%s.zip", task.Identifier))
			// 创建目录
			parentFolder := filepath.Dir(filePath)
			e := util.MkdirIfNotExists(parentFolder)
			if e != nil {
				logger.Error("创建目录%s失败！\n", parentFolder)
				logger.ErrorLine(e.Error())
				pool.Retry(task)
				return
			}
			// 创建下载任务
			fetchTask := gopher_fetch.NewSimpleMonoGetTask(task.Url, filePath)
			// 订阅并更新下载状态
			fetchTask.SubscribeStatus(func(status *gopher_fetch.TaskStatus) {
				realTimeLogger.StatusMap.Store(task.Identifier, status)
			})
			// 开始下载
			logger.Info("正在下载：%s...\033[K\n", task.Identifier)
			e = fetchTask.Run()
			// 下载完成移除任务列表
			realTimeLogger.StatusMap.Delete(task.Identifier)
			if e != nil {
				logger.Error("下载%s时出错！原因：%s，后续将重新下载...\033[K\n", task.Identifier, e)
				pool.Retry(task)
				return
			}
			logger.Info("影像：%s下载完成！\033[K\n", task.Identifier)
			// 检查摘要
			if global.TotalConfig.Download.CheckFile {
				if task.Md5 == "" {
					logger.Warn("影像：%s没有摘要信息，跳过校验...\033[K\n", task.Identifier)
				} else {
					md5Check, e := fetchTask.CheckFile(gopher_fetch.ChecksumMd5, task.Md5)
					logger.Info("校验下载的影像：%s...\033[K\n", task.Identifier)
					if e != nil {
						logger.Error("校验MD5失败！\033[K")
						logger.Error("%s\033[K\n", e.Error())
						pool.Retry(task)
						return
					}
					if !md5Check {
						logger.Error("MD5不匹配！将后续重新下载...\033[K\n")
						pool.Retry(task)
						return
					}
				}
			}
		},
		// 停机逻辑
		func(pool *tp.TaskPool[*model.GranuleTask]) {
			logger.Warn("接收到中断信号，即将完成最后的任务进度保存...\033[K\n")
			e := pool.SaveTaskList(taskFilePath)
			if e != nil {
				logger.Error("保存任务失败！%s\033[K\n", e)
			}
		},
		// 实时查看任务池任务的回调函数
		func(pool *tp.TaskPool[*model.GranuleTask]) {
			// 实时输出进度
			realTimeLogger.RealTimePrint()
		})
	// 开启自动进度保存
	fetchTaskPool.EnableTaskAutoSave(taskFilePath, 20*time.Second)
	// 启动并发任务池
	fetchTaskPool.Start()
	return !fetchTaskPool.IsInterrupt()
}

func main() {
	color.HiGreen("Sentinel Fetch 下载工具 v2.0.4")
	// 配置GopherFetch
	gopher_fetch.ConfigEnableLogger(false)
	gopher_fetch.ConfigEnvironmentProxy()
	// 解析下载任务
	var taskList []*model.GranuleTask
	if util.FileExists(taskFilePath) {
		sclog.InfoLine("任务进度文件存在！将读取进度并继续...")
		var e error
		taskList, e = tp.LoadTaskFile[*model.GranuleTask](taskFilePath)
		if e != nil {
			sclog.ErrorLine(e.Error())
			return
		}
	} else {
		sclog.WarnLine("任务进度文件不存在！将读取原始下载列表并创建任务...")
		taskList = getDownloadUrl()
	}
	// 执行下载任务
	taskDone := fetchFiles(taskList)
	// 全部下载完成后，删除任务进度文件
	if taskDone {
		_ = os.Remove(taskFilePath)
		sclog.Info("已删除任务进度文件：%s\n", taskFilePath)
		sclog.InfoLine("全部下载任务完成！")
	}
	// 关闭Token刷新
	util.DisableTokenRefresh()
}